/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.main.tapestry.services;

import java.io.IOException;

import org.apache.tapestry5.Asset2;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.annotations.Path;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.annotations.Decorate;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Order;
import org.apache.tapestry5.ioc.annotations.SubModule;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.ComponentSource;
import org.apache.tapestry5.services.ExceptionReporter;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestExceptionHandler;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.Response;
import org.apache.tapestry5.services.ResponseRenderer;
import org.libermundi.theorcs.chronicles.tapestry.services.ChronicleModule;
import org.libermundi.theorcs.comet.tapestry.services.CometModule;
import org.libermundi.theorcs.core.model.Node;
import org.libermundi.theorcs.core.model.NodeConstrain;
import org.libermundi.theorcs.core.model.NodeFactory;
import org.libermundi.theorcs.core.tapestry.services.AppHelper;
import org.libermundi.theorcs.core.tapestry.services.CoreModule;
import org.libermundi.theorcs.layout.LayoutConstants;
import org.libermundi.theorcs.layout.model.menu.NavMenuItem;
import org.libermundi.theorcs.layout.model.menu.NavMenuItemType;
import org.libermundi.theorcs.layout.model.menu.StaticNavMenuItem;
import org.libermundi.theorcs.layout.services.NavItemSource;
import org.libermundi.theorcs.layout.tapestry.model.menu.ImageNavMenuItem;
import org.libermundi.theorcs.layout.tapestry.services.LayoutAppHelper;
import org.libermundi.theorcs.layout.tapestry.services.LayoutModule;
import org.libermundi.theorcs.main.tapestry.pages.Index;
import org.libermundi.theorcs.main.tapestry.pages.Profile;
import org.libermundi.theorcs.security.SecurityConstants;
import org.libermundi.theorcs.security.model.impl.RoleNodeConstrain;
import org.libermundi.theorcs.security.tapestry.services.SecurityModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This module is automatically included as part of the Tapestry IoC Registry, it's a good place to
 * configure and extend Tapestry, or to place your own service definitions.
 */
@SubModule({SecurityModule.class,CoreModule.class,
	LayoutModule.class, ChronicleModule.class, CometModule.class})
public class TheOrcsModule {
    /**
     * This is a service definition, the service will be named "TimingFilter". The interface,
     * RequestFilter, is used within the RequestHandler service pipeline, which is built from the
     * RequestHandler service configuration. Tapestry IoC is responsible for passing in an
     * appropriate Logger instance. Requests for static resources are handled at a higher level, so
     * this filter will only be invoked for Tapestry related requests.
     * 
     * <p>
     * Service builder methods are useful when the implementation is inline as an inner class
     * (as here) or require some other kind of special initialization. In most cases,
     * use the static bind() method instead. 
     * 
     * <p>
     * If this method was named "build", then the service id would be taken from the 
     * service interface and would be "RequestFilter".  Since Tapestry already defines
     * a service named "RequestFilter" we use an explicit service id that we can reference
     * inside the contribution method.
     */    
    public RequestFilter buildTimingFilter(final Logger log) {
        return new RequestFilter()
        {
            @Override
			public boolean service(Request request, Response response, RequestHandler handler)
                    throws IOException
            {
                long startTime = System.currentTimeMillis();

                try
                {
                    // The responsibility of a filter is to invoke the corresponding method
                    // in the handler. When you chain multiple filters together, each filter
                    // received a handler that is a bridge to the next filter.
                    
                    return handler.service(request, response);
                }
                finally
                {
                    long elapsed = System.currentTimeMillis() - startTime;
                    log.info(String.format("Request time: %d ms for %s", elapsed, request.getPath()));
                }
            }
        };
    }

    /**
     * This is a contribution to the RequestHandler service configuration. This is how we extend
     * Tapestry using the timing filter. A common use for this kind of filter is transaction
     * management or security. The @Local annotation selects the desired service by type, but only
     * from the same module.  Without @Local, there would be an error due to the other service(s)
     * that implement RequestFilter (defined in other modules).
     */
    public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
            @Local
            RequestFilter filter) {
        // Each contribution to an ordered configuration has a name, When necessary, you may
        // set constraints to precisely control the invocation order of the contributed filter
        // within the pipeline.
        
        configuration.add("Timing", filter);
    }
    
    @SuppressWarnings("rawtypes")
	public static void contributeMenuService(OrderedConfiguration<Node> configuration,
			Messages messages, LayoutAppHelper layoutHelper,
			@Inject @Path("${layout.images}/TheORCS-logo.png") Asset2 orcsLogo,
			AppHelper appHelper,
			@InjectService("ChronicleNavItemSource") NavItemSource chronicleNavItemSource) {		
		//Top Left Menu in Chronicle

    	// Add TheORCS Logo (Default) //TODO => Add the possibility to Override from Platform configuration
    	Node<NavMenuItem> logo = NodeFactory.getNode("LOGO_HOME_ORCS");
	    	logo.setData(new ImageNavMenuItem(orcsLogo));
	    	logo.getData().setMainPageId(Index.PAGE_ID);
	    	logo.getData().setDescription("message:main.mainmenu.homepage.hint"); // will be used by the title of the "a" element
	    	logo.getData().setLabel("message:main.mainmenu.homepage"); // will be used by the alt of the "img" element
	    	logo.getData().setURI(appHelper.getPageURI(Index.class));
	    	logo.setParentId(LayoutConstants.PORTAL_TOP_LEFT_MENU_ID); // Link to the Main Portal Menu (Left)
    	
    	configuration.add("logo", logo);
    	
    	// Add Chronicle List in the Menu
    	Node<NavMenuItem> chronicles = layoutHelper.buildDynamicMenuNode(LayoutConstants.PORTAL_TOP_LEFT_MENU_ID,
    			chronicleNavItemSource, "message:main.mainmenu.chronicles", "/",
    			new RoleNodeConstrain(SecurityConstants.ROLE_USER));
	    	chronicles.getData().setDescription("message:main.mainmenu.chronicles.hint");
	    	chronicles.getData().setCss("icon-film icon-white");
    	configuration.add("chronicles", chronicles, "after:logo");
    	
    	//Top Right Menu in Chronicle
    	
    	// Add the Search Form
    	Node<NavMenuItem> search = NodeFactory.getNode("SEARCH_FIELD");
	    	search.setData(new StaticNavMenuItem(NavMenuItemType.SEARCHFORM));
	    	search.getData().setMainPageId("SEARCH_FIELD");
	    	search.setParentId(LayoutConstants.PORTAL_TOP_RIGHT_MENU_ID); // Link to the Main Portal Menu (Right)
    	configuration.add("search",search,"after:chronicles");

    	// Add Profile Link
		Node<NavMenuItem> profile = NodeFactory.getNode();
			profile.setData(new StaticNavMenuItem(NavMenuItemType.ICON));
			profile.getData().setMainPageId(Profile.PAGE_ID);
			profile.getData().setDescription("message:main.mainmenu.profile.hint");
			profile.getData().setCss("icon-user icon-white");
			profile.getData().setURI(appHelper.getPageURI(Profile.class));
			profile.addConstrain(new RoleNodeConstrain(SecurityConstants.ROLE_USER));
			profile.setParentId(LayoutConstants.PORTAL_TOP_RIGHT_MENU_ID); // Link to the Main Chronicles Menu (Right)

		configuration.add("profile", profile,"after:search");
		
		// Add Admin Link
		Node<NavMenuItem> admin = NodeFactory.getNode();
			admin.setData(new StaticNavMenuItem(NavMenuItemType.ICON));
			admin.getData().setMainPageId(org.libermundi.theorcs.main.tapestry.pages.admin.Index.PAGE_ID);
			admin.getData().setDescription("message:main.mainmenu.admin.hint");
			admin.getData().setCss("icon-lock icon-white");
			admin.getData().setURI(appHelper.getPageURI(org.libermundi.theorcs.main.tapestry.pages.admin.Index.class));
			admin.setConstrainMode(NodeConstrain.Mode.ANY);
			admin.addConstrain(new RoleNodeConstrain(SecurityConstants.ROLE_ADMIN));
			admin.addConstrain(new RoleNodeConstrain(SecurityConstants.ROLE_ROOT));
			admin.setParentId(LayoutConstants.PORTAL_TOP_RIGHT_MENU_ID); // Link to the Main Chronicles Menu (Right)

		configuration.add("admin", admin, "after:profile");    	
    	
    	// Add the Login Box
    	Node<NavMenuItem> login = NodeFactory.getNode("LOGIN_BOX");
	    	login.setData(new StaticNavMenuItem(NavMenuItemType.LOGINBOX));
	    	login.getData().setMainPageId("LOGIN_BOX");
	    	login.setParentId(LayoutConstants.PORTAL_TOP_RIGHT_MENU_ID); // Link to the Main Portal Menu (Right)
    	configuration.add("login", login, "after:admin");
    	
    }
    
	public static void contributeComponentMessagesSource(
			@Value("/META-INF/lang/main.properties")
			Resource mainCatalogResource,
			OrderedConfiguration<Resource> configuration) {
			configuration.add("mainCatalog", mainCatalogResource, "before:AppCatalog");
	}
	
	// handle Unexpected RuntimeExceptions
	@Decorate(serviceInterface=RequestExceptionHandler.class,id="finalExceptionHandler")
	@Order("after:*")
    public static RequestExceptionHandler decorateRequestExceptionHandler(
    				final RequestExceptionHandler delegate,
    				final ResponseRenderer renderer,
    				@Symbol(SymbolConstants.PRODUCTION_MODE)
    	            boolean productionMode,
    	            final ComponentSource componentSource,
    				final ComponentClassResolver resolver) {
    	
    	if (!productionMode){
    		return null; // Let us work ^^
    	}
    	
    	// Here we redirect to the ErrorPage of the Application
        return new RequestExceptionHandler() {
        	private final Logger logger = LoggerFactory.getLogger(RequestExceptionHandler.class);
            @Override
			public void handleRequestException(Throwable exception) throws IOException {
            	if(logger.isDebugEnabled()) {
            		logger.debug("---------- RequestException RequestExceptionHandler Final Decorator ----------");
            	}
            	
            	logger.error("Unexpected runtime exception: " + exception.getMessage(), exception);
                ExceptionReporter index = (ExceptionReporter) componentSource.getPage("Error");

                index.reportException(exception);

                renderer.renderPageMarkupResponse("Error");
            }
       };
    }    	
}
